#include <map>
#include "FunctionDefinition.h"
#include "../Exceptions.h"
#include "ExpressionType.h"
#include "../GeneralVisitor.h"
#include "../Constants.h"

FunctionDefinition::FunctionDefinition(string iFunctionName, vector<Variable*> iInputVariables, TreeNode *iBody, bool iIsBuiltIn)
{
    this->fFunctionName = iFunctionName;
    this->fInputVariables = iInputVariables;
    this->fBody = iBody;
    this->fIsBuiltIn = iIsBuiltIn;
    this->fBytecodeOffset = 0;
}

FunctionDefinition::~FunctionDefinition()
{
    delete this->fBody;

    for(vector<Variable*>::iterator it = this->fInputVariables.begin(); it != fInputVariables.end(); ++it)
    {
        delete (*it);
    }

    this->fInputVariables.clear();
    this->fLocalVars.clear();
}

void FunctionDefinition::ResolveVariables()
{
    set<string>::iterator lVarInSet;
    set<string>::iterator lNotFound;

    // nejdriv zkontrolovat vstupni parametry, jestli nejake nemaji stejne jmeno...
    for(vector<Variable*>::iterator it = this->fInputVariables.begin(); it != fInputVariables.end(); ++it)
    {
        string lVarName = (*it)->GetVarName();

        lNotFound = fLocalVars.end();
        lVarInSet = fLocalVars.find(lVarName);

        if(lVarInSet != lNotFound)
        {
            throw DuplicateParameterNameException(this->fFunctionName, lVarName);
        }

        fLocalVars.insert(lVarName);
    }

    // Ted projdi telo a zkontroluj promenne.
    // Kdyz narazis na promennou, ktera neni deklarovana, vyhod vyjimku.
    // Kdyz nasazis na uzel VarDeclaration, pridej deklarovanou promennou do mnoziny.
    // Protoze se mnozina vsude predava referenci, nasledujici prikazy a vyrazy uz promennou uvidi jako deklarovanou.
    // How nice & simple...
    fBody->CheckVariables(fLocalVars, this->fFunctionName);

    if(fLocalVars.size() > MAX_FUNCTION_LOCAL_VARS)
        throw TooMuchLocalVars(fFunctionName, MAX_FUNCTION_LOCAL_VARS, fLocalVars.size());
}

void FunctionDefinition::ResolveFunctionCalls(set<FunctionDefinition*>& iAllFunctions)
{
    fBody->CheckFunctionCalls(iAllFunctions, this->fFunctionName);
}

void FunctionDefinition::ResolveClassConstructions(set<ClassDefinition*>& iAllClasses)
{
    fBody->CheckClassConstructions(iAllClasses, this->fFunctionName);
}

void FunctionDefinition::CheckExpressionTypes()
{
    map<string, ExpressionType*> lVarTypes;

    for(set<string>::iterator it = fLocalVars.begin(); it != fLocalVars.end(); ++it)
    {
        lVarTypes[*it] = new ExpressionType(Unresolved);
    }

    try
    {
        fBody->CheckExpressionTypes(lVarTypes, this->fFunctionName);
    }
    catch(BaseException&)
    {
        for(map<string, ExpressionType*>::iterator it = lVarTypes.begin(); it != lVarTypes.end(); ++it)
        {
            delete (*it).second;
        }

        throw;
    }

    for(map<string, ExpressionType*>::iterator it = lVarTypes.begin(); it != lVarTypes.end(); ++it)
    {
        delete (*it).second;
    }

    lVarTypes.clear();
}

ReturnStatement* FunctionDefinition::GetReturnStatement()
{
    return fBody->GetFirstReturnStatement();
}

void FunctionDefinition::CheckBuiltInFunctionCallParams(map<string, ExpressionType*>& iVarTypes, vector<TreeNode*>& iCallParams)
{
    ExpressionType* lType;
    ValueType lValType;

    switch(this->fBuiltInFunctionType)
    {
        case ArrayFunction: // konstruktor pole prijima jeden parametr typu LongInteger
            lType = iCallParams[0]->GetType(iVarTypes, this->fFunctionName);
            lValType = lType->fType;

            if(lType->fResolveTypeAtRuntime)
            {
                delete lType;
                return;
            }

            if(lValType != LongInteger)
            {
                delete lType;
                throw InvalidTypeException(this->fFunctionName, "konstruktor pole", LongInteger, lValType);
            }

            delete lType;
            break;

        default:
            break;
    }
}

ExpressionType* FunctionDefinition::GetBuiltInFunctionReturnType()
{
    switch(this->fBuiltInFunctionType)
    {
        case ArrayFunction:
            return new ExpressionType(Array, false);

        default:
            return new ExpressionType(Unresolved, true);
    }
}

void FunctionDefinition::Accept(GeneralVisitor* iVisitor)
{
    iVisitor->Visit(this);
}

vector<string> FunctionDefinition::GetInputVarNames()
{
    vector<string> lResult;

    for(vector<Variable*>::iterator it = fInputVariables.begin(); it != fInputVariables.end(); ++it)
    {
        lResult.push_back((*it)->GetVarName());
    }

    return lResult;
}

vector<string> FunctionDefinition::GetLocalVarNames()
{
    vector<string> lResult;

    // Problem je, ze v fLocalVars jsou zahrnuty i vstupni parametry - musime to osetrit.
    for(set<string>::iterator it = fLocalVars.begin(); it != fLocalVars.end(); ++it)
    {
        bool lIsParam = false;

        for(vector<Variable*>::iterator it2 = fInputVariables.begin(); it2 != fInputVariables.end(); ++it2)
        {
            if((*it) == (*it2)->GetVarName())
            {
                lIsParam = true;
                break;
            }
        }

        if(!lIsParam)
            lResult.push_back(*it);
    }

    return lResult;
}
